package cn.gem.rainbow.api;

import cn.gem.rainbow.cfg.SystemParam;
import cn.gem.rainbow.cfg.restful.APIException;
import cn.gem.rainbow.cfg.restful.ResultCodeEnum;
import cn.gem.rainbow.cfg.token.JwtUtil;
import cn.gem.rainbow.cfg.token.TokenUser;
import cn.gem.rainbow.pojo.entity.SysPermission;
import cn.gem.rainbow.pojo.entity.SysRole;
import cn.gem.rainbow.pojo.entity.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * https://www.jianshu.com/p/7f724bec3dc3
 *
 *
 * 不用token,仅使用shiro，其实已经足够 保证调用 后端API的安全性了；
 * 但是，当前热门的SPA应用的需求是 要求后端校验Token,来验证前端页面操作的有效性，
 * 故 在前后端分离的背景下，SpringBoot除了要集成shiro 还要集成 JsonWebToken
 *
 */
@RestController
@Slf4j
public class LoginController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Autowired
    private SystemParam systemParam;

    /**
     * 建议设置
     * shiro会话有效期
     * 》= redis token key有效期
     * 》= token解析有效期
     *
     *
     * http://localhost:8080/rainbow/login?name=wsl&pass=123456
     * @param user
     * @return
     */
    @GetMapping("/login")
    public SysUser login(SysUser user) {
        if (StringUtils.isEmpty(user.getName()) || StringUtils.isEmpty(user.getPass())) {
            log.error("请输入用户名和密码");
            throw new APIException(ResultCodeEnum.NOT_LOGIN);
        }
        //用户认证信息
        Subject subject = SecurityUtils.getSubject();
        //永不过期,在登陆最开始加上
        //SecurityUtils.getSubject().getSession().setTimeout(-1000L);
        //其他时间 单位毫秒
        //SecurityUtils.getSubject().getSession().setTimeout(1800000);

        //shiro会话过期时间，设置3分钟-测试,可设置为与token过期相同的时间
        subject.getSession().setTimeout(systemParam.getAppSessionTimeoutSeconds()*1000);
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                user.getName(),
                user.getPass()
        );
        try {
            //进行验证，这里可以捕获异常，然后返回对应信息
            subject.login(usernamePasswordToken);
            //检查是否有登录权限/是否被禁用
            subject.checkPermissions("login");
//            subject.checkRole("admin");
//            subject.checkPermissions("query", "add");
        } catch (UnknownAccountException e) {
            log.error("用户名不存在！", e);
            throw new APIException(ResultCodeEnum.ACCOUNT_NOT_EXIST);
        } catch (AuthenticationException e) {
            log.error("账号或密码错误！", e);
            throw new APIException(ResultCodeEnum.ACCOUNT_PASS_FAILED);
        } catch (AuthorizationException e) {
            log.error("没有权限！", e);
            throw new APIException(ResultCodeEnum.ACCOUNT_DISABLED);
        }

        log.info(usernamePasswordToken.getUsername()); //wsl 就是用户名
        log.info(usernamePasswordToken.getPrincipal().toString()); //wsl 就是用户名


        //应该查询数据库获得权限/角色/用户
//        SysPermission permissions1 = new SysPermission(1, "query");
//        SysPermission permissions2 = new SysPermission(2, "add");
//        Set<SysPermission> permissionsSet = new HashSet<>();
//        permissionsSet.add(permissions1);
//        permissionsSet.add(permissions2);
//        SysRole role = new SysRole(1, "admin", permissionsSet);
//        Set<SysRole> roleSet = new HashSet<>();
//        roleSet.add(role);
//        SysUser userBack = new SysUser(5, "wsl", "123456", roleSet);
        //生成token,并放入redis数据库,用redis key timeout来辅助token过期消失
        String tokenStr = JwtUtil.createJwt(TokenUser.builder()
                .userId(5L)
                .userName(usernamePasswordToken.getUsername())
                .build());
//        userBack.setToken(tokenStr);
        stringRedisTemplate.opsForValue().set(user.getName(), tokenStr,systemParam.getAppSessionTimeoutSeconds(), TimeUnit.SECONDS);
        //token返给前端后，之后的前端请求必须都把token带到请求头上，以保证SPA应用的幂等性/安全性
        SysUser userBack = SysUser.builder()
                .name(user.getName())
                .token(tokenStr).build();
        return userBack;
    }

    @GetMapping("/userInfo")
    public SysUser getUserInfo(String name){
        //应该查询数据库获得权限/角色/用户
        SysPermission permissions1 = new SysPermission(1, "query");
        SysPermission permissions2 = new SysPermission(2, "add");
        Set<SysPermission> permissionsSet = new HashSet<>();
        permissionsSet.add(permissions1);
        permissionsSet.add(permissions2);
        SysRole role = new SysRole(1, "admin", permissionsSet);
        Set<SysRole> roleSet = new HashSet<>();
        roleSet.add(role);
        SysUser userBack = new SysUser(5, "wsl", "123456", roleSet);
        return userBack;
    }

    @RequiresRoles("admin")
    @GetMapping("/admin")
    public String admin() {
        return "admin success!";
    }

    @RequiresPermissions("query")
    @GetMapping("/index")
    public String index() {
        return "index success!";
    }

    @RequiresPermissions("add")
    @GetMapping("/add")
    public String add() {
        return "add success!";
    }
}
